# import copy
from ltp.ltp import (
    getResultFromSentence
)
from neo4j_db.kgqa.knowledgeGraphQuestionAnswer import (
    getNodeByNameOrKeyword,
    matchTwoNodeAndReturnRelationship
)

from utils.wrapData import (
    wrapKGQAData,
    wrapFailure,
    wrapSuccess
)

from utils.error import (
    ERROR_QUESTION_INVALID
)

from utils.convert import (
    convertRelationshipsToNodeAndRelationship
)

VALID_NODE_TYPE_TAG = [
    "n",
    "nh",
    "ns"
]

def addGroupAndClear(groupList, group):
    copyGroup = group[:]
    groupList.append(copyGroup)
    group.clear()
'''
    思路:根据特定的词进行划分,分程多个nodes,其中每个nodes表示一个集体group
    比如:安妮和酒桶是法师吗?
    会把(安妮\酒桶)归为一个nodes集体,把法师归为另一个nodes集体,全部以数组的形式存入nodesGroupList中
'''
# 根据ltp的词性标注，过滤掉无效的tag
# 逻辑：
# 按照每个结点集进行划分，假设划分了三个节点集合a,b,c
# 那么a结点集合的所有结点都要与b集合有关联，不然答案为否定
# 同时b集合的所有结点都要与c集合有关联,不然答案也为否定
def processTagListAndGetNodesGroupList(wordHasTagList):
    nodesGroupList = []
    nodeGroup = []
    length = len(wordHasTagList)
    # 1.遍历链表
    for i in range(length):
        # 2.获取需要的数据
        item = wordHasTagList[i]
        tag = item['tag']
        word = item['word']
        # 3.根据不同的逻辑进行判断
        # 3.1 动词
        if tag == 'v':
            # 3.1.1 如果动词为一个字，有两种情况
            if len(word) == 1:
                # 3.1.1.1 
                # 特殊判断,下路:会被切分成[下,路]，这个时候因为下路是一个结点
                # 因此需要进行这个结点的判断并生成对应的结点加入到当前的集合当中
                if word == '下' and wordHasTagList[i + 1]['word'] == '路':
                    nodes = getNodeByNameOrKeyword("下路")
                    i += 1;
                    nodeGroup += nodes
                else:
                # 3.1.1.2
                # 如果不是上面那种情况，那就直接结束，本次集合的结点存储
                    if len(nodeGroup) > 0:
                        addGroupAndClear(nodesGroupList, nodeGroup)
                    continue
            # 3.1.2 动词为两个字可能是模型没识别出来是名词，比如：打野
            else:
                nodes = getNodeByNameOrKeyword(word)
                # 3.1.2.1 如果该节点没有找到,意味着这些词就是单纯的动词
                # 直接进行下一个节点集合的获取
                if nodes == None:
                    if len(nodeGroup) > 0:
                        addGroupAndClear(nodesGroupList, nodeGroup)
                    continue
                # 3.1.2.2 否侧直接将这个结点存入
                nodeGroup += nodes
        # 3.2 符合的结点的词: 名词\地点词\人名\
        # 长度大于1的动词，名词，位置词都要进行结点存取,这个时候只需要判断结点是否存在就可以了
        elif tag in VALID_NODE_TYPE_TAG:
            nodes = getNodeByNameOrKeyword(word)
            if nodes != None:
                nodeGroup += nodes
        # p:在\把,例如:酒桶在上路会遇到剑魔吗?在这个词就需要切开集合了
        elif tag == 'p':
            if len(nodeGroup) > 0:
                addGroupAndClear(nodesGroupList, nodeGroup)
    # 4.
    # 遍历完了之后需要判断nodeGroup里面是否还有结点,有就直接放入结果的数组里面去就好了
    if len(nodeGroup) > 0:
        addGroupAndClear(nodesGroupList, nodeGroup)
    # 5.因为按组进行划分,如果只有一组的话,是不能构成联系的,也就是这个提问本身有问题
    print("一共有%d组" % len(nodesGroupList))
    # print(nodesGroupList)
    if len(nodesGroupList) > 1:
        return nodesGroupList
    else:
        return None

# 用于初始化数组:用来判断最终的查询结果
# 1.nextNode表示这个节点对下一层任一节点的联系，有联系就是True，没联系就是False
# 2.lastNode表示这个节点对上一层任一节点的联系，有联系就是True，没联系就是False
def createInitNodeConnectResult(rowLength, colLengthArr):
    nodeConnectResult = []
    for i in range(rowLength):
        colInitArr = []
        for j in range(colLengthArr[i]):
            # 第一层, 只用判断该层节点对后续节点的关联
            if i == 0:
                colInitArr.append({
                    "nextNode": False
                })
            # 最后一层, 只用判断该层节点对前续节点的关联
            elif i == rowLength - 1:
                colInitArr.append({
                    "lastNode": False
                })
            # 中间层, 只用判断该层节点对前续、后续节点的关联
            else:
                colInitArr.append({
                    "nextNode": False,
                    "lastNode": False
                })
        nodeConnectResult.append(colInitArr[:])
    return nodeConnectResult

# 关于所有每个集合中的结点与下一个集合的结点的联系
# 1.所有都有联系
# 每个集合的结点以及所有相关边
# 2.部分有联系
# 把有联系的边给出,同时对于没有联系的点,展示其相关的边,结点不超过5个
# 3.一点都没有联系
# 直接给出否定答案, 不要去获取相关的数据
def findRelationshipFromGroupList(nodeGroupList):
    # 1.创建一个二维数组,里面全部初始值为False,表示对应节点与下一个结点是否有联系
    # 这里用于后续操作,前提条件时部分有联系的节点
    # 如果一个节点和其前面以及后面的节点没有交集,就会单独去查找它的自己的边
    rowLength = len(nodeGroupList)
    colLengthArr = [len(row) for row in nodeGroupList]
    nodeConnectResult = createInitNodeConnectResult(rowLength = rowLength, colLengthArr = colLengthArr)
    relationshipsResult = []
    # 行遍历
    for i in range(rowLength - 1):
        # 获取前面的节点集合
        for j in range(colLengthArr[i]):
            # 获取后面的节点集合
            for k in range(colLengthArr[i + 1]):
                frontNode = nodeGroupList[i][j]
                endNode = nodeGroupList[i + 1][k]
                relationships = matchTwoNodeAndReturnRelationship(frontNode, endNode)
                # 如果两个节点有关系,需要initConnection数组的对应位置设置为True，表示这个节点有联系，
                if len(relationships) != 0:
                    nodeConnectResult[i][j]['nextNode'] = True
                    nodeConnectResult[i + 1][k]['lastNode'] = True
                    relationshipsResult += relationships

    # 所有节点都有联系，结果自然返回正确
    allNodeHasConnect = True
    # 所有节点都没有联系，直接返回错误答案
    allNodeNotConnect = True
    # 用来存储完全与前后节点无联系的节点
    noConnectedNodesArr = []
    for i in range(len(nodeConnectResult)):
        rowArr = nodeConnectResult[i]
        for j in range(len(rowArr)):
            item = rowArr[j]
            noConnected = True
            for key in item:
                if item[key] == True:
                    noConnected = False
                    if allNodeNotConnect:
                        allNodeNotConnect = False
                if item[key] == False and allNodeHasConnect:
                    allNodeHasConnect = False
            # 装载那些完全没有联系的节点
            if noConnected:
                noConnectedNodesArr.append(nodeGroupList[i][j])
    # 都没有联系，就直接寄，数据不用给，直接否定即可
    if allNodeNotConnect:
        return wrapKGQAData(answer = "否定", answerType = False)
    # 都有联系，说明这个问句的答案是肯定的，直接赋值边的数据即可
    elif allNodeHasConnect:
        relationshipsData = convertRelationshipsToNodeAndRelationship(relationshipsResult)
        return wrapKGQAData(answer = "肯定", answerType = True, relationships = relationshipsData)
    # 部分正确，这个时候就只需要找到那些没有联系的节点，自己的联系就可以了，这里限制五个（太多的话，展示会有点问题）
    else:
        for node in noConnectedNodesArr:
            relationships = matchTwoNodeAndReturnRelationship(node, None)[:5]
            relationshipsResult += relationships
            relationshipsData = convertRelationshipsToNodeAndRelationship(relationshipsResult)
        return wrapKGQAData(answer = "否定", answerType = False, relationships = relationshipsData)
"""
数据格式：
    data: {
        "answer": "",
        "answerType": true/false,
        "relationships": [

        ],
    }
"""
# 获取答案
def getQuestionAnswer(sentence):
    wordHasTagList = getResultFromSentence(sentence)
    nodeGroupList = processTagListAndGetNodesGroupList(wordHasTagList)
    if nodeGroupList == None:
        return wrapFailure(**ERROR_QUESTION_INVALID)
    result = findRelationshipFromGroupList(nodeGroupList)
    return wrapSuccess(result)

